package com.ylr.basic.framework.service.impl;

import com.ylr.basic.common.enums.AesOperateFlowEnum;
import com.ylr.basic.common.exception.AesException;
import com.ylr.basic.framework.service.AesOperateFlowService;
import com.ylr.basic.framework.service.AesService;
import com.ylr.base.common.constant.Constant;
import com.ylr.base.common.enums.CommonEnum;
import com.ylr.base.common.enums.ModuleEnum;
import com.ylr.base.common.po.user.UserPo;
import com.ylr.base.common.utils.ByteUtils;
import com.ylr.base.common.utils.DataUtils;
import com.ylr.base.framework.service.AsyncService;
import com.ylr.base.common.utils.HttpUtils;
import com.ylr.base.common.utils.UserUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.*;

/**
 * Created by IntelliJ IDEA.
 *
 * @author wei
 * date: 2023-02-28 14:15:53
 * className: AesServiceImpl AES加解密服务接口实现类
 * version: 1.0
 * description:
 */
@Service("aesService")
public class AesServiceImpl implements AesService {

    private static final Logger log = LoggerFactory.getLogger(AesServiceImpl.class);

    /**
     * 加密密钥
     * 如果配置文件没有配置，则该值为空
     */
    @Value("${aes.secret}")
    private String secret;

    /**
     * iv偏移量
     * 如果配置文件没有配置，则该值为空
     */
    @Value("${aes.ivValue}")
    private String ivValue;

    /**
     * 加密对象
     */
    private volatile Cipher encryptCipher;

    /**
     * 解密对象
     */
    private volatile Cipher decodeCipher;

    private final AsyncService asyncService;

    private final AesOperateFlowService aesOperateFlowService;

    public AesServiceImpl(AsyncService asyncService, AesOperateFlowService aesOperateFlowService) {
        this.asyncService = asyncService;
        this.aesOperateFlowService = aesOperateFlowService;
    }

    /**
     * 初始化加密和解密对象
     */
    @PostConstruct
    private void initCipher() {
        // 只有开启加解密，才会初始化加解密对象
        try {
            this.encryptCipher = this.getCipher(Cipher.ENCRYPT_MODE);
            this.decodeCipher = this.getCipher(Cipher.DECRYPT_MODE);
        } catch (Exception e) {
            log.error("AesService 初始化异常。message={}", e.getMessage());
        }
    }

    @Override
    public String encryptToHex(String plainText) {

        String remark = StringUtils.EMPTY;
        String cipherText = StringUtils.EMPTY;
        try {
            this.initEncodeCipher();

            // 执行明文加密操作
            byte[] bytes = encryptCipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
            // 将字节数组转换成十六进制数字
            cipherText = ByteUtils.bytesToHex(bytes);

            log.debug("AES加密，CBC模式加密原文（{}）得到十六进制数字密文（{}）", plainText, cipherText);

            return cipherText;
        } catch (IllegalBlockSizeException | BadPaddingException e) {
            remark = "AES加密成十六进制密文异常";

            // 加密操作异常
            throw new AesException("明文（" + plainText + "），加密成十六进制密文异常。message={}" + e.getMessage());
        } finally {
            // 保存加密操作记录
            this.saveOperateFlow(AesOperateFlowEnum.ENCRYPT_HEX.getValue(), cipherText, remark);
        }
    }

    @Override
    public Map<?, String> batchEncryptToHex(Map<?, String> paramMap) {
        Map<Object, String> resultMap = new HashMap<>();
        for (Map.Entry<?, String> entry : paramMap.entrySet()) {
            // 数据加密
            String cipherText = this.encryptToHex(entry.getValue());
            // 记录数据
            resultMap.put(entry.getKey(), cipherText);
        }
        return resultMap;
    }

    @Override
    public String decodeHex(String cipherText) {
        String remark = StringUtils.EMPTY;
        String plainText = StringUtils.EMPTY;
        try {
            this.initDecodeCipher();

            // 将十六进制数字转换成字节数组密文
            byte[] bytes = ByteUtils.hexToBytes(cipherText);
            // 对字节数组密文进行解密操作
            bytes = decodeCipher.doFinal(bytes);
            // 得到解密后的字符串信息
            plainText = new String(bytes, StandardCharsets.UTF_8);

            log.debug("AES解密，CBC模式解密十六进制数字密文（{}）得到明文（{}）", cipherText, plainText);

            return plainText;
        } catch (IllegalBlockSizeException | BadPaddingException e) {
            remark = "AES解密十六进制密文异常";

            // AES解密异常
            throw new AesException("十六进制密文（" + cipherText + "）解密异常。message={}" + e.getMessage());
        } finally {
            // 保存加密操作记录
            this.saveOperateFlow(AesOperateFlowEnum.DECODE_HEX.getValue(), StringUtils.EMPTY, remark);
        }
    }

    @Override
    public String subDecodeHex(String cipherText, Integer start, Integer end) {
        String remark = StringUtils.EMPTY;
        String plainText = StringUtils.EMPTY;
        try {
            this.initDecodeCipher();

            // 将十六进制数字转换成字节数组密文
            byte[] bytes = ByteUtils.hexToBytes(cipherText);
            // 对字节数组密文进行解密操作
            bytes = decodeCipher.doFinal(bytes);
            // 得到解密后的字符串信息
            plainText = new String(bytes, StandardCharsets.UTF_8);
            // 数据脱敏
            plainText = DataUtils.dataMask(plainText, start, end);

            log.debug("AES解密，CBC模式解密十六进制数字密文（{}）得到明文（{}）", cipherText, plainText);

            return plainText;
        } catch (IllegalBlockSizeException | BadPaddingException e) {
            remark = "AES半解密十六进制密文异常";

            // AES解密异常
            throw new AesException("十六进制密文（" + cipherText + "）半解密异常。message={}" + e.getMessage());
        } finally {
            // 保存加密操作记录
            this.saveOperateFlow(AesOperateFlowEnum.SUB_DECODE_HEX.getValue(), plainText, remark);
        }
    }

    @Override
    public Map<?, String> batchDecodeHex(Map<?, String> paramMap) {
        Map<Object, String> resultMap = new HashMap<>();
        for (Map.Entry<?, String> entry : paramMap.entrySet()) {
            // 数据解密
            String plainText = this.decodeHex(entry.getValue());
            // 记录数据
            resultMap.put(entry.getKey(), plainText);
        }
        return resultMap;
    }

    @Override
    public String encryptToBase64(String plainText) {
        String remark = StringUtils.EMPTY;
        String cipherText = StringUtils.EMPTY;
        try {
            this.initEncodeCipher();

            // 执行明文加密操作
            byte[] bytes = encryptCipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
            // 将密文字节数组转换成Base64密文
//            String cipherText = new BASE64Encoder().encode(bytes);
            cipherText = Base64.getEncoder().encodeToString(bytes);

            log.debug("AES加密，CBC模式加密原文（{}）得到Base64密文（{}）", plainText, cipherText);

            return cipherText;
        } catch (IllegalBlockSizeException | BadPaddingException e) {
            remark = "AES加密成Base64密文异常";

            // 加密操作异常
            throw new AesException("明文（" + plainText + "），加密成Base64密文异常。message={}" + e.getMessage());
        } finally {
            // 保存加密操作记录
            this.saveOperateFlow(AesOperateFlowEnum.ENCRYPT_BASE64.getValue(), cipherText, remark);
        }
    }

    @Override
    public String decodeBase64(String cipherText) {
        String remark = StringUtils.EMPTY;
        String plainText = StringUtils.EMPTY;
        try {
            this.initDecodeCipher();

            // 将Base64密文转换成字节数组密文
//            byte[] bytes = new BASE64Decoder().decodeBuffer(cipherText);
            byte[] bytes = Base64.getDecoder().decode(cipherText);
            // 对字节数组密文进行解密操作
            bytes = decodeCipher.doFinal(bytes);

            // 得到解密后的字符串信息
            plainText = new String(bytes, StandardCharsets.UTF_8);

            log.debug("AES解密，CBC模式解密Bae64密文（{}）得到明文（{}）", cipherText, plainText);

            return plainText;
        } catch (IllegalBlockSizeException | BadPaddingException e) {
            remark = "AES解密Base64密文异常";

            // AES解密异常
            throw new AesException("Base64密文（" + cipherText + "）解密异常。message={}" + e.getMessage());
        } finally {
            // 保存加密操作记录
            this.saveOperateFlow(AesOperateFlowEnum.DECODE_BASE64.getValue(), StringUtils.EMPTY, remark);
        }
    }

    @Override
    public String subDecodeBase64(String cipherText, Integer start, Integer end) {
        String remark = StringUtils.EMPTY;
        String plainText = StringUtils.EMPTY;
        try {
            this.initDecodeCipher();

            // 将Base64密文转换成字节数组密文
//            byte[] bytes = new BASE64Decoder().decodeBuffer(cipherText);
            byte[] bytes = Base64.getDecoder().decode(cipherText);
            // 对字节数组密文进行解密操作
            bytes = decodeCipher.doFinal(bytes);

            // 得到解密后的字符串信息
            plainText = new String(bytes, StandardCharsets.UTF_8);
            // 数据脱敏
            plainText = DataUtils.dataMask(plainText, start, end);

            log.debug("AES解密，CBC模式解密Bae64密文（{}）得到明文（{}）", cipherText, plainText);

            return plainText;
        } catch (IllegalBlockSizeException | BadPaddingException e) {
            remark = "AES半解密Base64密文异常";

            // AES解密异常
            throw new AesException("Base64密文（" + cipherText + "）半解密异常。message={}" + e.getMessage());
        } finally {
            // 保存加密操作记录
            this.saveOperateFlow(AesOperateFlowEnum.SUB_DECODE_BASE64.getValue(), plainText, remark);
        }
    }

    @Override
    public Map<?, String> batchDecodeBase64(Map<?, String> paramMap) {
        Map<Object, String> resultMap = new HashMap<>();
        for (Map.Entry<?, String> entry : paramMap.entrySet()) {
            // 数据解密
            String plainText = this.decodeBase64(entry.getValue());
            // 记录数据
            resultMap.put(entry.getKey(), plainText);
        }
        return resultMap;
    }

    /**
     * 记录aes操作流水
     * @param type   操作类型
     * @param result 操作结果
     * @param remark 备注
     */
    private void saveOperateFlow(Integer type, String result, String remark) {
        HttpServletRequest request = HttpUtils.getHttpServletRequest();

        asyncService.runAsync(() -> {
            String moduleCode = request.getHeader(Constant.SOURCE);
            Integer moduleValue = Optional.ofNullable(ModuleEnum.getModuleEnum(moduleCode)).map(ModuleEnum::getValue).orElse(-1);
            String requestId = MDC.get(Constant.TRACE_ID);
            Long userId = CommonEnum.DEFAULT_ID.getLongValue();
            String username = StringUtils.EMPTY;

            UserPo userPo = UserUtils.getUser();
            if (Objects.nonNull(userPo)) {
                userId = userPo.getId();
                username = userPo.getUsername();
            }
            log.debug("aes => module={} type={} result={} requestId={} remark={} userId={} username={}", moduleValue, type, result, requestId, remark, userId, username);
            try {
                // 保存操作流水
                aesOperateFlowService.saveFlow(moduleValue, type, result, requestId, remark, username, userId);
            } catch (Exception e) {
                log.error("保存操作流水异常。message={}", e.getMessage(), e);
            }
        });
    }

    /**
     * 初始化加密对象
     */
    private void initEncodeCipher() {
        if (encryptCipher == null) {
            synchronized (this) {
                if (encryptCipher == null) {
                    log.warn("加密对象为空，重新获取");
                    this.encryptCipher = this.getCipher(Cipher.ENCRYPT_MODE);
                }
            }
        }
    }

    /**
     * 判断是否初始化解密对象
     */
    private void initDecodeCipher() {
        if (decodeCipher == null) {
            synchronized (this) {
                if (decodeCipher == null) {
                    log.warn("解密对象为空，重新获取");
                    this.decodeCipher = this.getCipher(Cipher.DECRYPT_MODE);
                }
            }
        }
    }

    /**
     * 构造AES加解密对象
     */
    private Cipher getCipher(int type) {
        try {
            // 获取密钥的字节数组
            byte[] bytes = Arrays.copyOf(secret.getBytes(StandardCharsets.UTF_8), 16);
            // 获取密钥对象
            /*
                加密方式
             */
            String encryptType = "AES";
            SecretKeySpec secretKeySpec = new SecretKeySpec(bytes, encryptType);
            // 获取加密执行对象
            /*
                AES加密方法，CBC加密模式，PKCS5Padding补码方式
             */
            String cipherType = "AES/CBC/PKCS5Padding";
            Cipher cipher = Cipher.getInstance(cipherType);
            // 创建向量iv，增加加密强度
            IvParameterSpec ivParameterSpec = new IvParameterSpec(Arrays.copyOf(ivValue.getBytes(StandardCharsets.UTF_8), 16));
            // 初始化加密执行对象
            cipher.init(type, secretKeySpec, ivParameterSpec);

            return cipher;
        } catch (NoSuchPaddingException | NoSuchAlgorithmException e) {
            // 获取加解密方式异常
            throw new AesException("获取AES加解密对象异常。type=" + type + " message=" + e.getMessage());
        }  catch (InvalidAlgorithmParameterException | InvalidKeyException e) {
            // 初始化加解密对象异常
            throw new AesException("初始化AES加密对象异常。type=" + type + " message={}" + e.getMessage());
        }
    }

}
